import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JApplet;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.JLabel;
import java.awt.GridLayout;
import javax.swing.SwingConstants;


public class Part2 extends JApplet{
	
	private JFrame frame;
	
	Animation animT;
	Animation animE;
	Animation animK;
	
	class AnimationPanel extends JPanel {
		int frame = 0;
		int scale = 50;
		
		public AnimationPanel() {
			//setScale((int)(this.getWidth()/1200.0 * 50));
		}
		
		public void setFrame(int frame) {
			this.frame = frame;
			this.repaint();
		}
		
		public void setScale(int scale) {
			animT.p.setCenterX(-7 * scale);
			animE.p.setCenterX(0 * scale);
			animK.p.setCenterX(7 * scale);
			this.scale = scale;
			//this.repaint();
		}
		
		public void paintComponent(Graphics g) {
		    super.paintComponent(g);
		    setScale((int)(this.getWidth()/1200.0 * 50));
		    
		    int centerX = this.getWidth() / 2;
		    int centerY = this.getHeight() / 2;
		    
		    Graphics2D g2d = (Graphics2D)g;
		    BufferedImage b = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_ARGB);
		    
		    g2d.setBackground(Color.white);
			g2d.clearRect(0, 0, this.getWidth(), this.getHeight());
			g2d.setColor(Color.black);
			g2d.drawLine(0, this.getHeight()/2, this.getWidth(), this.getHeight()/2);
			g2d.drawLine(b.getWidth()/2, 0, b.getWidth()/2, b.getHeight());
			
			// Draw scale on axis lines
			// X Axis
			for (int x = this.getWidth()/2; x < this.getWidth(); x += scale) {
				g2d.drawLine(x, centerY - 5, x, centerY + 5);
			}
			for (int x = this.getWidth()/2; x > 0; x -= scale) {
				g2d.drawLine(x, centerY - 5, x, centerY + 5);
			}
			
			// Y Axis
			for (int y = this.getHeight()/2; y < this.getHeight(); y += scale) {
				g2d.drawLine(centerX - 5, y, centerX + 5, y);
			}
			for (int y = this.getHeight()/2; y > 0; y -= scale) {
				g2d.drawLine(centerX - 5, y, centerX + 5, y);
			}
		    
		    animT.draw(b, frame, scale);
			animE.draw(b, frame, scale);
			animK.draw(b, frame, scale);
			
			g2d.drawImage(b, 0, 0, null);
		}
	}
	
	/**
	 * Multiplies two matrices together and returns the result. 
	 * NOTE: The matrix {{3,2},{1,4}} is the following:
	 * 3 2 
	 * 1 4
	 * @param m1 The first matrix
	 * @param m2 The second matrix
	 * @return The resulting matrix
	 */
	public static double[][] multiply(double[][] m1, double[][] m2)
	{
		double[][] result = new double[m1.length][m2[0].length];
		
		  for(int i = 0; i < m1.length; i++) 
			  for(int j = 0; j < m2[0].length; j++) 
		    	  for(int k = 0; k < m1[0].length; k++)
		    		  result[i][j] += m1[i][k]*m2[k][j];
		  
		return result;
	}
	
	public double[][] scalarMultiply(double[][] m1, double val)
	{
		double[][] result = new double[m1.length][m1[0].length];
		
		  for(int i = 0; i < m1.length; i++) 
			  for(int j = 0; j < m1[0].length; j++) 
		    		 result[i][j] += m1[i][j]*val;
		  
		return result;
	}
	
	public String matrixToString(double[][] matrix) {
		String str = "";
		for (double[] singleDimension : matrix) {
			str += "[";
			for (double i: singleDimension) {
				str += i + ", ";
			}
			str += "]\n";
		}
		
		return str;
	}
	
	
	enum Axes { X, Y, Z };
	private class Animation {
		Polygon p;
		int numFrames;
		int numRotations;
		Axes axisOfRotation;
		int currentFrame;
		
		public Animation( Polygon p, int numFrames, int numRotations, Axes axisOfRotation) {
			this.p = p;
			this.numFrames = numFrames;
			this.numRotations = numRotations;
			this.axisOfRotation = axisOfRotation;
			this.currentFrame = 0;
		}
		
		public int getFrameNumber() {
			return currentFrame;
		}
		
		public Polygon getPolygon() {
			return this.p;
		}
		
		public void draw(BufferedImage b, int frame, int scale) {
			double rotation = (double)numRotations / (numFrames-1)  * 360 * frame;
			switch(axisOfRotation) {
			case X:
				p.xAxisTheta = Math.toRadians(rotation);
				break;
			case Y:
				p.yAxisTheta = Math.toRadians(rotation);
				break;
			case Z:
				p.zAxisTheta = Math.toRadians(rotation);
				break;
			}
			
			p.drawTo(b, frame, scale);
			this.currentFrame = frame;
		}
	}
	
	
	private class Vertex {
		double x, y;
		
		public Vertex(double x, double y, double z) {
			this.x = x;
			this.y = y;
		}
		
		public Vertex(double x, double y) {
			this(x, y, 0);
		}
		
		public Vertex rotate(double xTheta, double yTheta, double zTheta) {
			return this.rotateAboutX(xTheta).rotateAboutY(yTheta).rotateAboutZ(zTheta);
		}
		
		public Vertex rotateAboutX(double theta) {
			double[][] rotationMatrix = {
					{1, 0},
					{0, Math.cos(theta)}
			};
			
			return applyTransformationMatrix(rotationMatrix);
		}
		
		public Vertex rotateAboutY(double theta) {
			double[][] rotationMatrix = {
					{Math.cos(theta), 0},
					{0, 1}
			};
			
			return applyTransformationMatrix(rotationMatrix);
		}
		
		public Vertex rotateAboutZ(double theta) {
			double[][] rotationMatrix = {
					{Math.cos(theta), -Math.sin(theta)},
					{Math.sin(theta), Math.cos(theta)}
			};
			
			return applyTransformationMatrix(rotationMatrix);
		}
		
		public Vertex applyTransformationMatrix(double[][] transformationMatrix) {
			
			double[][] vertexMatrix = this.getMatrix();
			
			double[][] rotated = multiply(transformationMatrix, vertexMatrix);

			return new Vertex(rotated[0][0], rotated[1][0]);
		}
		
		public double[][] getMatrix() {
			return new double[][] {
					{x},
					{y}
			};
		}
	}
	
	private class Polygon {
		List<Vertex> vertices;
		double xAxisTheta, yAxisTheta, zAxisTheta;
		int centerX, centerY;
		List<Vertex> rotatedVertices;

		public void setCenterX(int x) {
			this.centerX = x;
		}
		
		public void setCenterY(int y) {
			this.centerY = y;
		}
		
		public Polygon(int centerX, int centerY, Vertex... vertices) {
			this.vertices = new ArrayList<Vertex>();
			
			double maxX = 0, maxY = 0;
			for (Vertex v : vertices) {
				if (v.x > maxX)
					maxX = v.x;
				if (v.y > maxY)
					maxY = v.y;
			}
			for (Vertex v : vertices) {
				v.x -= maxX/2;
				v.y -= maxY/2;
				this.vertices.add(v);
			}
			
			xAxisTheta = 0;
			yAxisTheta = 0;
			zAxisTheta = 0;
			this.centerX = centerX;
			this.centerY = centerY;
		}
		
		public void drawScaledLine(BufferedImage b, int scale, double x1, double y1, double x2, double y2) {
			Graphics2D g = b.createGraphics();
			g.setColor(Color.black);
			
			int centerX = b.getWidth() / 2 + this.centerX;
			int centerY = b.getHeight() / 2 - this.centerY;
			
			g.drawLine(centerX + (int)Math.round(x1*scale), centerY - (int)Math.round(y1*scale), centerX + (int)Math.round(x2*scale), centerY - (int)Math.round(y2*scale));
		}
		
		public List<Vertex> getRotatedVertices() {
			List<Vertex> rotatedVertices = new ArrayList<Vertex>();
			for (Vertex v : vertices) {
				rotatedVertices.add(v.rotate(xAxisTheta, yAxisTheta, zAxisTheta));
			}
			
			return rotatedVertices;
		}
		
		public void drawTo(BufferedImage b, int frame, int scale) {
			Graphics2D g = b.createGraphics();
			g.setColor(Color.black);
			
			System.out.println("Frame: " + frame);

			rotatedVertices = getRotatedVertices();
			
			Vertex v;
			
			for (int i = 1; i < rotatedVertices.size(); i++) {
				v = rotatedVertices.get(i);
				Vertex lastV = rotatedVertices.get(i - 1);
				System.out.println("Line " + lastV.x + " " + lastV.y + " to " + v.x + " " + v.y);
				drawScaledLine(b, scale, lastV.x, lastV.y, v.x, v.y);
				
			}
			
		}
	}

	
	public static void main(String[] args) {
		EventQueue.invokeLater(new Runnable() {
			public void run() {
				try {
					Part2 window = new Part2();
					window.frame.setVisible(true);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
	
	/**
	 * Create the application.
	 */
	public Part2() {
		initialize();
	}

	/**
	 * Initialize the contents of the frame.
	 */
	private void initialize() {
		frame = new JFrame();
		frame.setBounds(100, 100, 450, 300);

		final Polygon letterT = new Polygon(-350, 0, new Vertex(2, 0), new Vertex(4, 0),
				new Vertex(4, 4), new Vertex(6, 4), new Vertex(6, 6), new Vertex(0, 6),
				new Vertex(0, 4), new Vertex(2, 4), new Vertex(2, 0));
		
		final Polygon letterE = new Polygon(0, 0, new Vertex(0, 0), new Vertex(5, 0),
				new Vertex(5, 1), new Vertex(2, 1), new Vertex(2, 2), new Vertex(5, 2),
				new Vertex(5, 3), new Vertex(2, 3), new Vertex(2, 4), new Vertex(5, 4),
				new Vertex(5, 5), new Vertex(0, 5), new Vertex(0, 0));
		
		final Polygon letterK = new Polygon(350, 0, new Vertex(0, 0), new Vertex(2, 0),
				new Vertex(2, 2), new Vertex(4, 0), new Vertex(6, 0), new Vertex(3, 3),
				new Vertex(6, 6), new Vertex(4, 6), new Vertex(2, 4), new Vertex(2, 6),
				new Vertex(0, 6), new Vertex(0, 0));
		
		animT = new Animation(letterT, 121, 3, Axes.Z);
		animE = new Animation(letterE, 121, 2, Axes.Y);
		animK = new Animation(letterK, 121, 5, Axes.X);
		
		

		final AnimationPanel panel = new AnimationPanel();
		frame.getContentPane().add(panel, BorderLayout.CENTER);
		
		JPanel panel_1 = new JPanel();
		frame.getContentPane().add(panel_1, BorderLayout.SOUTH);
		panel_1.setLayout(new GridLayout(0, 3, 0, 0));
		
		final JLabel frameLabel = new JLabel("Frame:  ");
		frameLabel.setHorizontalAlignment(SwingConstants.RIGHT);
		panel_1.add(frameLabel);
		
		JSlider slider = new JSlider(0, 120, 0);
		slider.setMinorTickSpacing(1);
		slider.setPaintTicks(true);
		slider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent arg0) {
				JSlider source = (JSlider)arg0.getSource();
				
				panel.setFrame(source.getValue());
				panel.repaint();

				frameLabel.setText("Frame: " + animT.getFrameNumber());
			}
		});
		panel_1.add(slider);
		
		
		
	}
	
	
	// APPLET STUFF
	
	private AnimationPanel animationPanel = new AnimationPanel();

	private JSlider frameSlider = new JSlider(0, 120, 0);
	
	private JLabel frameLabel = new JLabel();
	
	private JLabel coordinatesLabel = new JLabel();
	String coordinatesStr;

	public void init() {
		Container cp = getContentPane();
		cp.add(animationPanel);
		
		frameLabel.setText("Frame: ");
		final DecimalFormat coordinateFormat = new DecimalFormat("0.000");
		
		frameSlider.setMajorTickSpacing(1);
		frameSlider.setPaintTicks(true);
		frameSlider.setSnapToTicks(true);
		
		frameSlider.addChangeListener(new ChangeListener() {
			public void stateChanged(ChangeEvent e) {
				int frame = ((JSlider) e.getSource()).getValue();
				((JSlider) e.getSource()).setValue(frame);
				animationPanel.setFrame(frame);
				frameLabel.setText("Frame: " + frame);
				
				// Write Vertex Coordinates to screen
				coordinatesStr = "<html>Coordinates:<br>Note: Release mouse before<br>reading these values<br>";
				// T
				coordinatesStr += "<br>Polygon T (" + coordinateFormat.format(animT.p.zAxisTheta) + " radians):<br>";
				for (Vertex v : animT.p.rotatedVertices) {
					coordinatesStr += "(" + coordinateFormat.format(v.x) + ", " + coordinateFormat.format(v.y) + ")<br>";
				}
				// E
				coordinatesStr += "<br>Polygon E (" + coordinateFormat.format(animE.p.yAxisTheta) + " radians):<br>";
				for (Vertex v : animE.p.rotatedVertices) {
					coordinatesStr += "(" + coordinateFormat.format(v.x) + ", " + coordinateFormat.format(v.y) + ")<br>";
				}
				// K
				coordinatesStr += "<br>Polygon K (" + coordinateFormat.format(animK.p.xAxisTheta) + " radians):<br>";
				for (Vertex v : animK.p.rotatedVertices) {
					coordinatesStr += "(" + coordinateFormat.format(v.x) + ", " + coordinateFormat.format(v.y) + ")<br>";
				}
				
				coordinatesStr += "</html>";
				//if (!((JSlider) e.getSource()).getValueIsAdjusting())
					coordinatesLabel.setText(coordinatesStr);
				coordinatesLabel.repaint();
			}
		});
		cp.add(BorderLayout.SOUTH, frameSlider);
		cp.add(BorderLayout.NORTH, frameLabel);
		cp.add(BorderLayout.EAST, coordinatesLabel);
	}
	
}
